// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 * Description : bonding vendor hook slb mode arp proc
 * Author :
 * Create :
 */

#include <net/ip.h>
#include <linux/etherdevice.h>
#include <linux/securec.h>
#include <linux/udp.h>
#include <linux/ip.h>
#include <linux/notifier.h>
#include <linux/icmpv6.h>
#include <linux/icmp.h>
#include <linux/tcp.h>
#include <linux/inetdevice.h>
#include <net/bonding.h>

#include "bond_slb.h"

int max_bonds = BOND_DEFAULT_MAX_BONDS;
int vendor_bond_slb_debug;
int arp_stale_time = BOND_SLB_ARP_STALE_TIME_DEFAULT;
int mc_group_id = BOND_SLB_MC_GROUP_ID_DEFAULT;

module_param(max_bonds, int, 0);
MODULE_PARM_DESC(max_bonds, "Max number of bonded devices (default 1)");

module_param(vendor_bond_slb_debug, int, 0640);
MODULE_PARM_DESC(vendor_bond_slb_debug, "For vendor bond slb debug mode; 0 for none (default), 1 for active");

module_param(arp_stale_time, int, 0);
MODULE_PARM_DESC(arp_stale_time, "arp stale time (only for SLB mode, default 20000)");

module_param(mc_group_id, int, 0);
MODULE_PARM_DESC(mc_group_id, "multicast group id (default 6)");

u8 slb_mac_multicast[ETH_ALEN] = {0x01, 0x0, 0x0, 0x0, 0x06, 0x0f};

struct slb_bond_info *g_bond_slb_info;

static DEFINE_SPINLOCK(slb_list_lock);
struct list_head g_slb_list_head;
int g_current_bonds;

void bond_slb_check_params(void)
{
	if (max_bonds < 0) {
		BOND_SLB_PRINTF("Warning: max_bonds (%d) not in range %d-%d, "
		"so it was reset to BOND_DEFAULT_MAX_BONDS (%d)",
		max_bonds, 0, INT_MAX, BOND_DEFAULT_MAX_BONDS);
		max_bonds = BOND_DEFAULT_MAX_BONDS;
	}

	if (arp_stale_time <= 0) {
		BOND_SLB_PRINTF("Warning: arp_stale_time (%d) not in range %d-%d, "
		"so it was reset to default (%d)",
		arp_stale_time, 1,
		INT_MAX, BOND_SLB_ARP_STALE_TIME_DEFAULT);
		arp_stale_time = BOND_SLB_ARP_STALE_TIME_DEFAULT;
	}

	if ((mc_group_id < 0) || (mc_group_id > BOND_SLB_MC_GROUP_ID_MAX)) {
		BOND_SLB_PRINTF("Warning: mc_group_id (%d) not in range %d-%d, "
		"so it was reset to default (%d)",
		mc_group_id, 0, BOND_SLB_MC_GROUP_ID_MAX,
		BOND_SLB_MC_GROUP_ID_DEFAULT);
		mc_group_id = BOND_SLB_MC_GROUP_ID_DEFAULT;
	}

	slb_mac_multicast[4] = (unsigned char)mc_group_id;
}

int bond_slb_get_mac_multicast(unsigned char *mac_addr, unsigned int len)
{
	if (len == ETH_ALEN) {
		ether_addr_copy(mac_addr, slb_mac_multicast);
		return 0;
	}

	return -1;
}

bool bond_slb_is_mac_multicast(unsigned char *mac_addr, unsigned int len)
{
	if (len == ETH_ALEN)
		return (memcmp(mac_addr, slb_mac_multicast, ETH_ALEN) == 0);

	return false;
}

int bond_slb_is_debug(void)
{
	if (vendor_bond_slb_debug == 1)
		return 1;
	return 0;
}

int bond_slb_get_arp_stale_time(void)
{
	return arp_stale_time;
}

void bond_slb_info_list_init(void)
{
	INIT_LIST_HEAD(&g_slb_list_head);
	g_current_bonds = 0;
}

struct slb_bond_info *bond_slb_info_get(struct bonding *bond)
{
	struct slb_bond_info *slb_info = NULL;
	struct slb_bond_info *item = NULL;
	struct list_head *l = NULL;
	struct list_head *n = NULL;

	if (bond == NULL)
		return NULL;

	list_for_each_safe(l, n, &g_slb_list_head) {
		item = list_entry(l, struct slb_bond_info, next_slb_info);
		if (item->bond == bond)
			slb_info = item;
	}

	return slb_info;
}

struct slb_bond_info *bond_slb_info_add(struct bonding *bond)
{
	struct slb_bond_info *slb_info = NULL;

	if (g_current_bonds >= max_bonds) {
		BOND_SLB_ERR_PRINTF("slb info has exceeded the maximum max_bonds:%d.", max_bonds);
		return NULL;
	}

	slb_info = kmalloc(sizeof(*slb_info), GFP_KERNEL);
	if (slb_info == NULL)
		return NULL;
	memset_s(slb_info, sizeof(struct slb_bond_info), 0, sizeof(struct slb_bond_info));
	slb_info->bond = bond;

	spin_lock_bh(&slb_list_lock);
	list_add_tail(&slb_info->next_slb_info, &g_slb_list_head);
	g_current_bonds++;
	spin_unlock_bh(&slb_list_lock);
	BOND_SLB_PRINTF("slb info add to list success size:%d.", g_current_bonds);
	return slb_info;
}

void bond_slb_info_del(struct bonding *bond)
{
	struct slb_bond_info *item = NULL;
	struct list_head *l = NULL;
	struct list_head *n = NULL;

	spin_lock_bh(&slb_list_lock);
	list_for_each_safe(l, n, &g_slb_list_head) {
		item = list_entry(l, struct slb_bond_info, next_slb_info);
		if (item->bond == bond) {
			list_del(&item->next_slb_info);
			kfree(item);
		}
	}
	g_current_bonds--;
	spin_unlock_bh(&slb_list_lock);
}

void bond_slb_open(struct bonding *bond, struct net_device *bond_dev)
{
	struct slb_bond_info *slb_info = bond_slb_info_get(bond);
	struct packet_type *pk_type = NULL;

	if ((slb_info != NULL) || (bond == NULL) || (bond_dev == NULL)) {
		BOND_SLB_ERR_PRINTF("init: slb has been init or invalid bond dev.");
		return;
	}

	slb_info = bond_slb_info_add(bond);
	if (slb_info == NULL) {
		BOND_SLB_ERR_PRINTF("Failed to allocate SLB info");
		return;
	}

	bond_slb_init_arp_hashtbl(bond, slb_info);

	/* initialize packet type */
	pk_type = &slb_info->slb_pkt_type;
	pk_type->type = htons(ETH_P_ARP);
	pk_type->dev  = bond->dev;
	pk_type->func = bond_slb_arp_recv;
	/* register to receive ARPs */
	dev_add_pack(pk_type);

	INIT_DELAYED_WORK(&slb_info->slb_work, bond_slb_arp_mon);
	queue_delayed_work(bond->wq, &slb_info->slb_work, 0);

	bond_dev->broadcast[0] = (unsigned char)0x01;
	bond_dev->broadcast[1] = (unsigned char)0x00;
	bond_dev->broadcast[2] = (unsigned char)0x00;
	bond_dev->broadcast[3] = (unsigned char)0x00;
	bond_dev->broadcast[4] = (unsigned char)mc_group_id;
	bond_dev->broadcast[5] = (unsigned char)0x0f;

	BOND_SLB_PRINTF("slb_init.");
}

void bond_slb_close(struct bonding *bond)
{
	struct slb_bond_info *slb_info = bond_slb_info_get(bond);

	if ((slb_info == NULL) || (bond == NULL)) {
		BOND_SLB_ERR_PRINTF("deinit: slb has not been init.");
		return;
	}

	cancel_delayed_work_sync(&slb_info->slb_work);

	bond_slb_deinit_arp_hashtbl(bond, slb_info);

	dev_remove_pack(&(slb_info->slb_pkt_type));

	bond_slb_info_del(bond);

	BOND_SLB_PRINTF("slb deinit done.");
}

void bond_slb_enslave(struct bonding *bond, struct slave *new_slave)
{
	struct slb_bond_info *slb_info = bond_slb_info_get(bond);

	if ((slb_info == NULL) || (new_slave == NULL)) {
		BOND_SLB_ERR_PRINTF("slotid: slb has not been init or invalid bond dev.");
		return;
	}

	slb_info->own_chassis = new_slave->dev->dev_addr[ETH_MAC_CHASSIS_INDEX];
	slb_info->own_slot = new_slave->dev->dev_addr[ETH_MAC_SLOT_INDEX];

	BOND_SLB_PRINTF("slb enslave(%s) slot id[%u/%u].", new_slave->dev->name,
					slb_info->own_chassis, slb_info->own_slot);
}

int __bond_slb_release_slave_arp(struct bonding *bond, struct net_device *slave_dev)
{
	int i = 0;
	struct slb_bond_info *bond_info = bond_slb_info_get(bond);
	struct slave *slave;
	struct hlist_head *head;
	struct hlist_node *node;
	struct slb_arp_request_info *arp_req = NULL;

	if (bond_info == NULL || bond_info->arp_request_hashtbl == NULL) {
		BOND_SLB_ERR_PRINTF("bond_slb_release_slave bond_info:%p  ", bond_info);
		return -1;
	}
	spin_lock_bh(&(bond_info->arp_request_hashtbl_lock));

	for (i = 0 ; i < SLB_HASH_TABLE_SIZE; i++) {
		head = &bond_info->arp_request_hashtbl[i];
		if (head == NULL)
			continue;
		hlist_for_each_entry_safe(arp_req, node, head, ar_list) {
			slave = arp_req->slave;
			if (slave && slave->dev == slave_dev)
				bond_slb_del_arp_req(bond, bond_info, arp_req);
		}
	}

	spin_unlock_bh(&(bond_info->arp_request_hashtbl_lock));
	return 0;
}

void bond_slb_release_slave(struct bonding *bond, struct net_device *slave_dev)
{
	BOND_SLB_PRINTF("unslave: slb release slave[%s]...", slave_dev->name);
	__bond_slb_release_slave_arp(bond, slave_dev);
}

int bond_slb_slave_event_change(struct bonding *bond, struct net_device *slave_dev)
{
	int ret;

	BOND_SLB_PRINTF("slave event: slb slave[%s] event change...", slave_dev->name);
	return __bond_slb_release_slave_arp(bond, slave_dev);
}

static inline u32 _slb_simple_hash(__be32 ip_addr)
{
	return ip_addr % SLB_HASH_TABLE_SIZE;
}

struct slb_arp_request_info *_slb_add_arp_req(struct hlist_head *head, __be32 ip_addr)
{
	struct slb_arp_request_info *arp_req = NULL;

	arp_req = kzalloc(sizeof(*arp_req), GFP_ATOMIC);
	if (!arp_req)
		return NULL;

	arp_req->ip_dst = ip_addr;
	hlist_add_head(&arp_req->ar_list, head);
	return arp_req;
}

void _slb_del_arp_req(struct slb_arp_request_info *arp_req)
{
	hlist_del_init(&arp_req->ar_list);
	kfree(arp_req);
}

struct slb_arp_request_info *bond_slb_find_arp_req(struct slb_bond_info *bond_info,
							__be32 ip_addr, int add)
{
	struct hlist_head *head;
	struct slb_arp_request_info *arp_req = NULL;

	if (bond_info == NULL || bond_info->arp_request_hashtbl == NULL) {
		BOND_SLB_ERR_PRINTF("slb_find_arp_req: bond_info or hashtbl is NULL");
		return NULL;
	}

	head = &bond_info->arp_request_hashtbl[_slb_simple_hash(ip_addr)];
	hlist_for_each_entry(arp_req, head, ar_list) {
		if (arp_req->ip_dst == ip_addr)
			return arp_req;
	}

	arp_req = NULL;
	if (add) {
		arp_req = _slb_add_arp_req(head, ip_addr);
		if (arp_req)
			bond_info->arp_request_count++;
	}

	return arp_req;
}

int bond_slb_arp_recv(struct sk_buff *skb, struct net_device *bond_dev,
					  struct packet_type *ptype, struct net_device *orig_dev)
{
	int res = NET_RX_DROP;
	struct slb_arp_request_info *arp_req;
	struct bonding *bond = netdev_priv(bond_dev);
	struct slb_bond_info *bond_info = bond_slb_info_get(bond);
	struct slb_arp_pkt *arp = slb_get_arp_pkt(skb);
	struct slave *rx_slave = NULL;
	u8 ip_dst[4] = { 0 };
	u8 ip_src[4] = { 0 };

	if ((bond_info == NULL) || (bond == NULL) || (arp == NULL)) {
		BOND_SLB_ERR_PRINTF("slb_recv: slb has not been init or pkt not arp.");
		dev_kfree_skb(skb);
		return res;
	}

	bond_slb_print_arppkt(arp);
	rx_slave = bond_get_slave_by_dev(bond, orig_dev);
	spin_lock_bh(&(bond_info->arp_request_hashtbl_lock));

	memcpy_s(ip_dst, sizeof(ip_dst), &(arp->ip_dst), sizeof(arp->ip_dst));
	memcpy_s(ip_src, sizeof(ip_src), &(arp->ip_src), sizeof(arp->ip_dst));
	if ((ip_dst[ETH_IP_CHASSIS_INDEX] != bond_info->own_chassis) ||
		(ip_dst[ETH_IP_SLOT_INDEX] != bond_info->own_slot)) {
		goto out;
	}

	arp_req = bond_slb_find_arp_req(bond_info, arp->ip_src, 1);
	if ((arp_req == NULL) || (rx_slave == NULL) || (dev_net(bond_dev) != &init_net) ||
		!(bond_dev->flags & IFF_MASTER)) {
		goto out;
	}

	if (skb->len < sizeof(struct slb_arp_pkt)) {
		BOND_SLB_DBG_PRINTF("Packet is too small to be an ARP");
		goto out;
	}

	if ((arp->op_code == htons(ARPOP_REPLY)) || (arp->op_code == htons(ARPOP_REQUEST))) {
		arp_req->assigned_by_peer = 1;
		arp_req->slave = rx_slave;
		arp_req->last_rx = 0;
		memcpy_s(arp_req->mac_dst, sizeof(arp_req->mac_dst), arp->mac_src, ETH_ALEN);
	}
	BOND_SLB_DBG_PRINTF("recv arp %d from %pI4 by %s",
						ntohs(arp->op_code), &arp->ip_src,
						rx_slave->dev->name);

	res = NET_RX_SUCCESS;

out:
	spin_unlock_bh(&(bond_info->arp_request_hashtbl_lock));
	dev_kfree_skb(skb);
	return res;
}

int bond_slb_init_arp_hashtbl(struct bonding *bond, struct slb_bond_info *slb_info)
{
	int size = SLB_HASH_TABLE_SIZE * sizeof(struct hlist_head);
	struct slb_bond_info *bond_info = slb_info;
	struct hlist_head *new_hashtbl = NULL;

	new_hashtbl = kmalloc(size, GFP_KERNEL);
	if (!new_hashtbl) {
		BOND_SLB_ERR_PRINTF("%s: Error: Failed to allocate SLB hash table\n",
							bond->dev->name);
		return -1;
	}
	spin_lock_init(&(bond_info->arp_request_hashtbl_lock));
	spin_lock_bh(&(bond_info->arp_request_hashtbl_lock));

	bond_info->arp_request_hashtbl = new_hashtbl;
	memset_s(new_hashtbl, size, 0, size);
	spin_unlock_bh(&(bond_info->arp_request_hashtbl_lock));

	BOND_SLB_PRINTF("slb init arp_hashtbl...");
	return 0;
}

int _slb_del_arp_entry(__be32 target, struct net_device *dev)
{
	int err = 0;
	__be32 ip = 0;
	struct neighbour *neigh = NULL;

	err = -ENXIO;
	ip = target;
	neigh = neigh_lookup(&arp_tbl, (const void *)&ip, dev);
	if (neigh) {
		if (neigh->nud_state & ~NUD_NOARP)
			err = neigh_update(neigh, NULL, NUD_FAILED,
							   NEIGH_UPDATE_F_OVERRIDE |
							   NEIGH_UPDATE_F_ADMIN, 0);
		neigh_release(neigh);
	}
	BOND_SLB_DBG_PRINTF("arp del addr %pI4, res %d", &ip, err);
	return err;
}

void bond_slb_del_arp_req(struct bonding *bond, struct slb_bond_info *slb_info,
						  struct slb_arp_request_info *arp_req)
{
	_slb_del_arp_entry(arp_req->ip_dst, bond->dev);
	_slb_del_arp_req(arp_req);
	slb_info->arp_request_count--;
}

void bond_slb_deinit_arp_hashtbl(struct bonding *bond, struct slb_bond_info *slb_info)
{
	struct hlist_head *head;
	struct hlist_node *node;
	struct slb_arp_request_info *arp_req = NULL;
	u32 i;

	BOND_SLB_PRINTF("bond slb deinit arphashtbl......");
	if (slb_info->arp_request_hashtbl == NULL) {
		BOND_SLB_ERR_PRINTF("bond slb deinit hashtbl is NULL");
		return;
	}

	spin_lock_bh(&(slb_info->arp_request_hashtbl_lock));

	for (i = 0; i < SLB_HASH_TABLE_SIZE; i++) {
		head = &slb_info->arp_request_hashtbl[i];
		if (head == NULL)
			continue;
		hlist_for_each_entry_safe(arp_req, node, head, ar_list) {
			bond_slb_del_arp_req(bond, slb_info, arp_req);
		}
	}
	kfree(slb_info->arp_request_hashtbl);
	slb_info->arp_request_hashtbl = NULL;

	spin_unlock_bh(&(slb_info->arp_request_hashtbl_lock));
}

void bond_slb_arp_mon(struct work_struct *work)
{
	int delta_in_ticks = 0;
	int i = 0;
	struct slb_bond_info *bond_info = container_of(work, struct slb_bond_info, slb_work.work);
	struct slave *slave = NULL;
	struct hlist_head *head;
	struct hlist_node *node;
	struct slb_arp_request_info *arp_req = NULL;

	if ((bond_info == NULL) || (bond_info->bond == NULL) ||
		(bond_info->arp_request_hashtbl == NULL)) {
		BOND_SLB_ERR_PRINTF("slb arp mon bond_info is null");
		return;
	}

	delta_in_ticks = msecs_to_jiffies(bond_slb_get_arp_stale_time());
	if (!bond_has_slaves(bond_info->bond)) {
		BOND_SLB_DBG_PRINTF("slb arp mon bond[%s] has no slaves",
							bond_info->bond->dev->name);
		queue_delayed_work(bond_info->bond->wq, &bond_info->slb_work, delta_in_ticks);
		return;
	}

	rcu_read_lock();
	spin_lock_bh(&(bond_info->arp_request_hashtbl_lock));

	for (i = 0; i < SLB_HASH_TABLE_SIZE; i++) {
		head = &bond_info->arp_request_hashtbl[i];
		if (head == NULL)
			continue;
		hlist_for_each_entry_safe(arp_req, node, head, ar_list) {
			if (++arp_req->last_rx > 3) {
				bond_slb_del_arp_req(bond_info->bond, bond_info, arp_req);
				continue;
			}

			slave = arp_req->slave;
			if (!slave)
				continue;

			arp_send(ARPOP_REQUEST, ETH_P_ARP, arp_req->ip_dst,
					 slave->dev, bond_confirm_addr(bond_info->bond->dev, 0, 0),
					 (const unsigned char *)arp_req->mac_dst,
					 (const unsigned char *)(slave->dev->dev_addr),
					 NULL);
			BOND_SLB_DBG_PRINTF("mon send arp to %pI4 by %s",
								&arp_req->ip_dst, slave->dev->name);
		}
	}
	spin_unlock_bh(&(bond_info->arp_request_hashtbl_lock));
	rcu_read_unlock();

	queue_delayed_work(bond_info->bond->wq, &bond_info->slb_work, delta_in_ticks);
}

void bond_slb_send_arp_all_slave(struct bonding *bond, __be32 ip_addr)
{
	struct list_head *iter = NULL;
	struct slave *slave = NULL;
	unsigned char mac_multicast[ETH_ALEN] = { 0 };

	if (bond == NULL)
		return;

	bond_slb_get_mac_multicast(mac_multicast, ETH_ALEN);
	bond_for_each_slave_rcu(bond, slave, iter) {
		if (bond_slave_can_tx(slave)) {
			arp_send(ARPOP_REQUEST, ETH_P_ARP, ip_addr, slave->dev,
					 bond_confirm_addr(bond->dev, 0, 0),
					 (const unsigned char *)mac_multicast,
					 (const unsigned char *)(slave->dev->dev_addr), NULL);
		}
	}
}

/* show arp_entry. */
void bond_slb_procfs_show_arp(struct bonding *bond, struct seq_file *seq)
{
	struct slb_bond_info *bond_info = bond_slb_info_get(bond);
	struct hlist_head *head = NULL;
	struct slb_arp_request_info *arp_req = NULL;
	char ibuf[20] = { 0 };
	char mbuf[20] = { 0 };
	struct list_head *iter = NULL;
	struct slave *slave = NULL;
	int i;

	if (!bond_info->arp_request_count)
		return;

	seq_puts(seq, "\nAddress          HWaddress          Iface  Flag\n");
	spin_lock_bh(&(bond_info->arp_request_hashtbl_lock));

	for (i = 0; i < SLB_HASH_TABLE_SIZE; i++) {
		head = &bond_info->arp_request_hashtbl[i];
		hlist_for_each_entry(arp_req, head, ar_list) {
			(void)snprintf_s(ibuf, sizeof(ibuf), sizeof(ibuf) - 1,
							 "%pI4", &arp_req->ip_dst);
			(void)snprintf_s(mbuf, sizeof(mbuf), sizeof(mbuf) - 1,
							 "%pM", arp_req->mac_dst);
			seq_printf(seq, "%-16s %s  %-5s  %u\n", ibuf, mbuf,
				arp_req->slave ? arp_req->slave->dev->name : "",
				arp_req->assigned_by_peer);
		}
	}
	BOND_SLB_PRINTF("bond0 priv_flags: 0x%x features:0x%llx vlan_features:0x%llx flag:0x%x",
					bond->dev->priv_flags, bond->dev->features,
					bond->dev->vlan_features, bond->dev->flags);
	bond_for_each_slave_rcu(bond, slave, iter) {
		BOND_SLB_PRINTF("slave:%s priv_flags: 0x%x features:0x%llx "
		"vlan_features:0x%llx flag:0x%x",
		slave->dev->name, slave->dev->priv_flags,
		slave->dev->features, slave->dev->vlan_features,
		slave->dev->flags);
	}
	spin_unlock_bh(&(bond_info->arp_request_hashtbl_lock));
}

void bond_slb_print_arppkt(struct slb_arp_pkt *arp)
{
	const u8 *buf = (const u8 *)arp;
	char log_buf[BOND_SLB_LOGMSG_MAX_LEN] = { 0 };
	size_t len = 0;
	int i = 0;

	if (bond_slb_is_debug()) {
		len = snprintf_s(log_buf, sizeof(log_buf), sizeof(log_buf) - 1,
						 "arp->hw_addr_space=%04x\n"
						 "arp->prot_addr_space=%04x\n"
						 "arp->hw_addr_len=%02x\n"
						 "arp->prot_addr_len=%02x\n"
						 "arp->op_code=%04x\n",
						 arp->hw_addr_space, arp->prot_addr_space,
						 arp->hw_addr_len, arp->prot_addr_len,
						 arp->op_code);
		len += snprintf_s(log_buf + len, sizeof(log_buf) - len, sizeof(log_buf) - 1 - len,
						  "arp->ip_src=0x%08x\narp->ip_dst=0x%08x\n",
						  arp->ip_src, arp->ip_dst);
		for (i = 0; i < sizeof(struct slb_arp_pkt); i++)
			len += snprintf_s(log_buf + len, sizeof(log_buf) - len,
							  sizeof(log_buf) - 1 - len,
							  "%02x ", buf[i]);
		len += snprintf_s(log_buf + len, sizeof(log_buf) - len,
							  sizeof(log_buf) - 1 - len,
							  "%02x ", buf[i]);
		BOND_SLB_DBG_PRINTF("slb_arp_pkt buf:\n----------------\n%s\n----------------",
							log_buf);
	}
}

u8 slb_mac_bcast[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

struct slave *slb_choose_first_slave(struct bonding *bond, u32	selector)
{
	struct slave *assigned_slave = NULL;
	struct slave *first_slave = bond_first_slave_rcu(bond);
	struct list_head *iter = NULL;
	struct slave *slave = NULL;
	bool found = false;

	if (bond->slave_cnt == 1 || selector == 0) {
		if (bond_slave_can_tx(first_slave))
			assigned_slave = first_slave;
	} else if (bond->slave_cnt > 1) {
		bond_for_each_slave_rcu(bond, slave, iter) {
			if ((first_slave != slave) && bond_slave_can_tx(slave)) {
				assigned_slave = slave;
				found = true;
				break;
			}
		}
		if (!found && bond_slave_can_tx(first_slave))
			assigned_slave = first_slave;
	}
	return assigned_slave;
}

struct slave *slb_choose_next_slave(struct bonding *bond, struct slave *tx_slave)
{
	struct list_head *iter = NULL;
	struct slave *slave = NULL;

	if (bond->slave_cnt == 0 || tx_slave == NULL) {
		return NULL;
	} else if (bond->slave_cnt == 1) {
		if (bond_slave_can_tx(tx_slave))
			return tx_slave;
	} else if (bond->slave_cnt > 1) {
		bond_for_each_slave_rcu(bond, slave, iter) {
			if ((tx_slave != slave) && bond_slave_can_tx(slave))
				return slave;
		}
		return tx_slave;
	}
	return NULL;
}

struct slave *slb_arp_xmit_get_slave(struct sk_buff *skb, struct bonding *bond, int *drop)
{
	struct slb_arp_request_info *arp_req;
	struct slb_arp_pkt *arp = slb_get_arp_pkt(skb);
	struct ethhdr *eth_data = eth_hdr(skb);
	struct slb_bond_info *bond_info = bond_slb_info_get(bond);
	struct slave *tx_slave = NULL;

	*drop = 0;
	bond_slb_print_arppkt(arp);
	if (arp->op_code == htons(ARPOP_REQUEST)) {
		arp_req = bond_slb_find_arp_req(bond_info, arp->ip_dst, 1);
		if (!arp_req)
			return NULL;

		arp_req->assigned_by_peer = 0;

		if (!arp_req->slave) {
			u32 selector = (ntohl(arp->ip_dst) ^ ntohl(arp->ip_src) ^ jiffies);

			arp_req->slave = slb_choose_first_slave(bond, selector & 1);
			BOND_SLB_DBG_PRINTF("choose first slave");
		} else {
			arp_req->slave = slb_choose_next_slave(bond, arp_req->slave);
		}
		tx_slave = arp_req->slave;
		bond_slb_send_arp_all_slave(bond, arp->ip_dst);
	} else if (arp->op_code == htons(ARPOP_REPLY)) {
		arp_req = bond_slb_find_arp_req(bond_info, arp->ip_dst, 0);
		if (!arp_req)
			return NULL;

		tx_slave = arp_req->slave;
	}

	if (tx_slave) {
		if (arp_req->assigned_by_peer &&
			!bond_slb_is_mac_multicast(eth_data->h_dest, ETH_ALEN))
			memcpy_s(eth_data->h_dest, sizeof(eth_data->h_dest),
					 arp_req->mac_dst, ETH_ALEN);
		memcpy_s(arp->mac_src, sizeof(arp->mac_src), tx_slave->dev->dev_addr, ETH_ALEN);
		BOND_SLB_DBG_PRINTF("xmit arp %d to %pI4 by %s", ntohs(arp->op_code),
							&arp_req->ip_dst, tx_slave->dev->name);
	} else {
		BOND_SLB_DBG_PRINTF("slb arp xmit is failed!");
	}

	return tx_slave;
}

netdev_tx_t bond_slb_dev_queue_xmit(struct bonding *bond,
		struct sk_buff *skb, struct net_device *slave_dev)
{
	skb->dev = slave_dev;
	u16 slave_mapping = qdisc_skb_cb(skb)->slave_dev_queue_mapping;

	BUILD_BUG_ON(sizeof(skb->queue_mapping) != sizeof(slave_mapping));
	skb_set_queue_mapping(skb, slave_mapping);

	if (unlikely(netpoll_tx_running(bond->dev)))
		return bond_netpoll_send_skb(bond_get_slave_by_dev(bond, slave_dev), skb);

	return dev_queue_xmit(skb);
}

struct slave *slb_ip_xmit_get_slave(struct sk_buff *skb, struct bonding *bond, int *drop)
{
	static const __be32 ip_bcast = htonl(0xffffffff);
	struct ethhdr *eth_data = eth_hdr(skb);
	struct iphdr *iph = ip_hdr(skb);
	struct slb_arp_request_info *arp_req = NULL;
	struct slave *tx_slave = NULL;
	struct slb_bond_info *bond_info = bond_slb_info_get(bond);

	if ((memcmp(eth_data->h_dest, slb_mac_bcast, ETH_ALEN) == 0) || (iph->daddr == ip_bcast) ||
		(iph->protocol == IPPROTO_IGMP)) {
		return NULL;
	}

	arp_req = bond_slb_find_arp_req(bond_info, iph->daddr, 0);
	if (arp_req && arp_req->slave && arp_req->assigned_by_peer) {
		tx_slave = arp_req->slave;
		arp_req->last_tx_ip = 0;
		memcpy_s(eth_data->h_dest, sizeof(eth_data->h_dest), arp_req->mac_dst, ETH_ALEN);
	} else {
		bond_slb_send_arp_all_slave(bond, iph->daddr);
	}
	return tx_slave;
}

netdev_tx_t bond_slb_xmit(struct sk_buff *skb, struct net_device *bond_dev)
{
	struct bonding *bond = netdev_priv(bond_dev);
	struct ethhdr *eth_data = NULL;
	struct slb_bond_info *bond_info = bond_slb_info_get(bond);
	struct slave *tx_slave = NULL;
	netdev_tx_t ret = NETDEV_TX_BUSY;
	int drop = 0;

	if ((bond_info == NULL) || (bond == NULL)) {
		BOND_SLB_ERR_PRINTF("xmit: slb has not been init or invalid bond dev.");
		goto out;
	}

	skb_reset_mac_header(skb);
	eth_data = eth_hdr(skb);

	/* make sure that the curr_active_slave and the slaves list do not change during tx */
	if (!(bond_has_slaves(bond) && (bond->dev->flags & IFF_UP))) {
		BOND_SLB_DBG_PRINTF("xmit: BOND_IS_OK check is failed!\n");
		goto out;
	}

	spin_lock_bh(&(bond_info->arp_request_hashtbl_lock));
	switch (ntohs(skb->protocol)) {
	case ETH_P_IP:
		tx_slave = slb_ip_xmit_get_slave(skb, bond, &drop);
		break;
	case ETH_P_ARP:
		tx_slave = slb_arp_xmit_get_slave(skb, bond, &drop);
		break;
	default:
		break;
	}
	spin_unlock_bh(&(bond_info->arp_request_hashtbl_lock));

	if (drop)
		goto out;

	if (!tx_slave)
		tx_slave = rcu_dereference(bond->curr_active_slave);

	if (tx_slave && bond_slave_can_tx(tx_slave)) {
		memcpy_s(eth_data->h_source, sizeof(eth_data->h_source),
				 tx_slave->dev->dev_addr, ETH_ALEN);
		ret = bond_slb_dev_queue_xmit(bond, skb, tx_slave->dev);
		BOND_SLB_DBG_PRINTF(" bond_dev_queue_xmit by netdev %s!", tx_slave->dev->name);
		return ret;
	}
	BOND_SLB_DBG_PRINTF("tx slave status check is failed.");

out:
	return bond_tx_drop(bond->dev, skb);
}

struct slave *bond_slb_xmit_slave_get(struct bonding *bond, struct sk_buff *skb)
{
	return NULL;
}
